package info.niwota.group;

import info.niwota.group.riff.WaveRecorder;

import java.io.File;
import java.io.IOException;

import android.media.MediaPlayer;
import android.media.MediaRecorder;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.os.Bundle;

public class Recorder implements OnCompletionListener, OnErrorListener {
	static final String SAMPLE_PREFIX = "voice-";
	
	static final String SAMPLE_PATH_KEY = "sample_path";
	static final String SAMPLE_LENGTH_KEY = "sample_length";

	public static final int IDLE_STATE = 0;
	public static final int RECORDING_STATE = 1;
	public static final int PLAYING_STATE = 2;

	int mState = IDLE_STATE;

	public static final int NO_ERROR = 0;
	public static final int SDCARD_ACCESS_ERROR = 1;
	public static final int INTERNAL_ERROR = 2;

	public interface OnStateChangedListener {
		public void onStateChanged(int state);

		public void onError(int error);
	}

	OnStateChangedListener mOnStateChangedListener = null;

	long mSampleStart = 0; // time at which latest record or play operation
							// started
	int mSampleDuration = 0; // length of current sample (in seconds)
	File mSampleFile = null;

	//
	AudioRecorder mRecorder = null;
	AudioPlayer  mPlayer = null;
	private int mBitRate;
	
	public Recorder() {
	}

	public void saveState(Bundle recorderState) {
		recorderState.putString(SAMPLE_PATH_KEY, mSampleFile.getAbsolutePath());
		recorderState.putInt(SAMPLE_LENGTH_KEY, mSampleDuration);
	}

	public int getMaxAmplitude() {
		if (mState != RECORDING_STATE) {
			return 0;
		}
		return mRecorder.getMaxAmplitude();
	}

	public void restoreState(Bundle recorderState) {
		String path = recorderState.getString(SAMPLE_PATH_KEY);
		if (path == null) {
			return;
		}
		int duration = recorderState.getInt(SAMPLE_LENGTH_KEY, -1);
		if (duration == -1) {
			return;
		}

		File f = new File(path);
		if (!f.exists()) {
			return;
		}
		if (mSampleFile != null
				&& mSampleFile.getAbsolutePath().compareTo(
						f.getAbsolutePath()) == 0) {
			return;
		}

		delete();
		mSampleFile = f;
		mSampleDuration = duration;

		signalStateChanged(IDLE_STATE);
	}

	public void setOnStateChangedListener(OnStateChangedListener listener) {
		mOnStateChangedListener = listener;
	}

	public int state() {
		return mState;
	}

	public int progress() {
		if (mState == RECORDING_STATE || mState == PLAYING_STATE)
			return (int) ((System.currentTimeMillis() - mSampleStart) / 1000);
		return 0;
	}

	public int sampleDuration() {
		return mSampleDuration;
	}

	public File sampleFile() {
		return mSampleFile;
	}

	/**
	 * Resets the recorder state. If a sample was recorded, the file is deleted.
	 */
	public void delete() {
		stop();

		if (mSampleFile != null) {
			mSampleFile.delete();
		}
		mSampleFile = null;
		mSampleDuration = 0;

		signalStateChanged(IDLE_STATE);
	}

	/**
	 * Resets the recorder state. If a sample was recorded, the file is left on
	 * disk and will be reused for a new recording.
	 */
	public void clear() {
		stop();

		mSampleDuration = 0;

		signalStateChanged(IDLE_STATE);
	}

	public void startRecording(File sub, String mimeType, String ext, int sampleRate) {
		stop();
		
		try {
			//new, format or path changed?
			if (mSampleFile == null 
				|| !mSampleFile.getName().endsWith(ext)
				|| !mSampleFile.getPath().startsWith(sub.getPath())) {
			
				if (mSampleFile != null) {
					mSampleFile.delete();
				}
				mSampleDuration = 0;
				mSampleFile = Files.getTempFile(sub, SAMPLE_PREFIX, ext);
			}
		} catch (IOException e) {
			setError(SDCARD_ACCESS_ERROR);
			return;
		}
		//create recorder
		int fmt = FileFormats.format(mimeType);
		switch(fmt) {
		case FileFormats.RIFF_WAV:
			mRecorder = new WaveRecorder();
			mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
			mRecorder.setOutputFormat(mimeType);
			mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
			break;
		default:
			mRecorder = new MyMediaRecorder();
			mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
			mRecorder.setOutputFormat(mimeType);
			mRecorder.setOutputFile(mSampleFile.getAbsolutePath());
			//must be set after previous values or illegal state exception will be thrown
			mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
		}
		mRecorder.setSampleRate(sampleRate);
		//
		try {
			mRecorder.prepare();
			//
			mBitRate = mRecorder.getBitRate();
		} catch (Exception exception) {
			setError(INTERNAL_ERROR);
			mRecorder.reset();
			mRecorder.release();
			mRecorder = null;
			return;
		}
		mRecorder.start();
		//
		mSampleStart = System.currentTimeMillis();
		setState(RECORDING_STATE);
	}

	public void stopRecording() {
		if (mRecorder == null) {
			return;
		}

		mRecorder.stop();
		mRecorder.release();
		mRecorder = null;

		mSampleDuration = (int) ((System.currentTimeMillis() - mSampleStart) / 1000);
		setState(IDLE_STATE);
	}

	public void startPlayback() {
		stop();
		
		mPlayer = new MyMediaPlayer();
		//
		try {
			mPlayer.setDataSource(mSampleFile.getAbsolutePath());
			mPlayer.setOnCompletionListener(this);
			mPlayer.setOnErrorListener(this);
			mPlayer.prepare();
			mPlayer.start();
		} catch (IllegalArgumentException e) {
			setError(INTERNAL_ERROR);
			mPlayer = null;
			return;
		} catch (IOException e) {
			setError(SDCARD_ACCESS_ERROR);
			mPlayer = null;
			return;
		}

		mSampleStart = System.currentTimeMillis();
		setState(PLAYING_STATE);
	}

	public void stopPlayback() {
		if (mPlayer == null) {
			// we were not in playback
			return;
		}
		mPlayer.stop();
		mPlayer.release();
		mPlayer = null;
		setState(IDLE_STATE);
	}

	public void stop() {
		stopRecording();
		stopPlayback();
	}

	public boolean onError(MediaPlayer mp, int what, int extra) {
		stop();
		setError(SDCARD_ACCESS_ERROR);
		return true;
	}

	public void onCompletion(MediaPlayer mp) {
		stop();
	}

	private void setState(int state) {
		if (state == mState) {
			return;
		}
		mState = state;
		signalStateChanged(mState);
	}

	private void signalStateChanged(int state) {
		if (mOnStateChangedListener != null) {
			mOnStateChangedListener.onStateChanged(state);
		}
	}

	private void setError(int error) {
		if (mOnStateChangedListener != null) {
			mOnStateChangedListener.onError(error);
		}
	}

	public int getBitRate() {
		return mBitRate;
	}
	
}
